package com.nsgf.library.utils;

import android.text.TextUtils;
import android.util.Log;

import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECPoint;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.security.SecureRandom;
import java.util.Formatter;

/**
 * SM2加密工具类（新版）
 * 支持JSON格式的公钥：{"cacheKey":"xxx","publicKey":"04xxx"}
 * 使用BouncyCastle实现SM2国密算法
 * 加密后会自动去掉密文开头的04前缀，与后台Hutool实现保持一致
 */
public class SM2Util {

    private static final String TAG = "SM2Util";

    // 初始化BouncyCastle Provider
    static {
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    // SM2公钥信息
    private String publicKey;
    private String cacheKey;

    /**
     * 构造函数 - 直接使用公钥和cacheKey
     *
     * @param publicKey SM2公钥（十六进制字符串，130位，以04开头）
     * @param cacheKey  缓存键（可选）
     */
    public SM2Util(String publicKey, String cacheKey) {
        this.publicKey = publicKey;
        this.cacheKey = cacheKey;
        validatePublicKey();
    }


    /**
     * 验证公钥格式
     */
    private void validatePublicKey() {
        if (TextUtils.isEmpty(publicKey)) {
            Log.e(TAG, "公钥为空");
            return;
        }

        String trimmedKey = publicKey.trim();
        if (trimmedKey.length() != 130) {
            Log.w(TAG, "警告：SM2公钥长度应为130位，当前长度：" + trimmedKey.length());
        }
        if (!trimmedKey.startsWith("04")) {
            Log.w(TAG, "警告：SM2公钥应以04开头（未压缩格式）");
        }
    }

    /**
     * 设置公钥（支持更新）
     *
     * @param publicKey SM2公钥（十六进制字符串，130位，以04开头）
     */
    public void setPublicKey(String publicKey) {
        this.publicKey = publicKey;
        validatePublicKey();
    }

    /**
     * 获取公钥
     *
     * @return SM2公钥
     */
    public String getPublicKey() {
        return publicKey;
    }

    /**
     * 获取cacheKey
     *
     * @return cacheKey
     */
    public String getCacheKey() {
        return cacheKey;
    }

    /**
     * SM2加密
     *
     * @param plainText 明文
     * @return 加密后的十六进制字符串
     */
    public String encrypt(String plainText) {
        if (TextUtils.isEmpty(plainText)) {
            Log.e(TAG, "明文为空");
            return "";
        }

        if (TextUtils.isEmpty(publicKey)) {
            Log.e(TAG, "SM2公钥未设置，无法加密");
            return "";
        }

        try {
            return encryptWithBC(plainText);
        } catch (Exception e) {
            Log.e(TAG, "SM2加密失败: " + e.getMessage(), e);
            e.printStackTrace();
            return "";
        }
    }

    /**
     * 使用BouncyCastle进行SM2加密
     * 使用C1C3C2模式（符合GM/T 0003-2012标准）
     */
    private String encryptWithBC(String plainText) throws Exception {
        // 1. 获取SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
        if (sm2ECParameters == null) {
            throw new RuntimeException("无法获取SM2曲线参数");
        }

        ECDomainParameters domainParameters = new ECDomainParameters(
                sm2ECParameters.getCurve(),
                sm2ECParameters.getG(),
                sm2ECParameters.getN()
        );

        // 2. 解析公钥
        String publicKeyHex = publicKey.trim().toLowerCase();

        // 验证公钥格式
        if (!publicKeyHex.startsWith("04")) {
            throw new IllegalArgumentException("SM2公钥必须以04开头");
        }

        if (publicKeyHex.length() != 130) {
            throw new IllegalArgumentException("SM2公钥长度必须为130位，当前：" + publicKeyHex.length());
        }

        // 去掉04前缀，提取X和Y坐标
        String xyHex = publicKeyHex.substring(2); // 去掉04，剩余128位

        // X坐标和Y坐标各64个十六进制字符（32字节）
        String xHex = xyHex.substring(0, 64);
        String yHex = xyHex.substring(64, 128);

        BigInteger x = new BigInteger(xHex, 16);
        BigInteger y = new BigInteger(yHex, 16);

        // 3. 创建ECPoint
        ECPoint ecPoint = sm2ECParameters.getCurve().createPoint(x, y);

        // 4. 创建公钥参数
        ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(ecPoint, domainParameters);

        // 5. 创建SM2Engine并使用C1C3C2模式（国密标准）
        SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);

        // 6. 初始化加密引擎
        ParametersWithRandom parametersWithRandom = new ParametersWithRandom(publicKeyParameters, new SecureRandom());
        sm2Engine.init(true, parametersWithRandom);

        // 7. 加密明文
        byte[] plainBytes = plainText.getBytes(StandardCharsets.UTF_8);
        byte[] encryptedBytes = sm2Engine.processBlock(plainBytes, 0, plainBytes.length);

        // 8. 转换为十六进制字符串
        String result = bytesToHex(encryptedBytes);

        // 9. 去掉密文开头的04前缀（与后台Hutool实现保持一致）
        // 后台代码：.encryptHex(data.getBytes(), KeyType.PublicKey).substring(2)
        if (result.length() >= 2 && result.startsWith("04")) {
            result = result.substring(2);
            Log.d(TAG, "已去掉密文开头的04前缀");
        }

        Log.d(TAG, "SM2加密成功，模式: C1C3C2, 明文长度: " + plainBytes.length +
                " 字节, 密文长度: " + result.length() + " 字符（已去掉04前缀）");

        return result;
    }

    /**
     * 字节数组转十六进制字符串
     */
    private String bytesToHex(byte[] bytes) {
        if (bytes == null || bytes.length == 0) {
            return "";
        }
        StringBuilder sb = new StringBuilder(bytes.length * 2);
        Formatter formatter = new Formatter(sb);
        for (byte b : bytes) {
            formatter.format("%02x", b);
        }
        return sb.toString();
    }

    /**
     * 静态方法：快速加密（使用公钥字符串）
     *
     * @param plainText 明文
     * @param publicKey SM2公钥（十六进制字符串，130位，以04开头）
     * @return 加密后的十六进制字符串
     */
    public static String encrypt(String plainText, String publicKey) {
        SM2Util crypto = new SM2Util(publicKey, null);
        return crypto.encrypt(plainText);
    }
}
